//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace Microsoft.Research.ScientificWorkflow.TridentUtilities
{
    #region Using Directives
    using System;
    using System.Collections.ObjectModel;
    using System.Reflection;
    using System.IO;
    #endregion

    /// <summary>
    /// This class contains the logic related to Assemblies load through Reflection.
    /// </summary>
    public static class AssemblyLoad
    {
        /// <summary>
        /// This function is used to get all the referenced assemblies 
        ///     of the parent assemblies.
        /// </summary>
        /// <param name="parentAssembly">
        /// Parent assembly for which the referenced assemblies has to be found.
        /// </param>
        /// <returns>
        /// Collection of all the referenced assemblies of the parent assembly.
        /// </returns>
        public static Collection<Assembly> LoadFrom(Assembly parentAssembly)
        {
            Collection<Assembly> referredAssemblies = new Collection<Assembly>();

            Load(referredAssemblies, parentAssembly);

            return referredAssemblies;
        }

        /// <summary>
        /// This function is used to load dll from the file.
        /// </summary>
        /// <param name="fileName"></param>
        /// <returns></returns>
        public static Assembly LoadFrom(string fileName)
        {
            Assembly depAsm = AssemblyLoad.SearchCurrentDomain(Path.GetFileNameWithoutExtension(fileName));
            if (depAsm == null)
            {
                depAsm = Assembly.LoadFrom(fileName);
            }

            return depAsm;
        }

        /// <summary>
        /// This function is used to get check whether the given 
        /// assembly is presently loaded in the current appdomain.
        /// 
        /// If assembly is loaded in the current appdoamin it returns the reference to that assembly.
        /// </summary>
        /// <param name="assemblyName">Assembly Name.</param>
        /// <returns>Reference to the assembly if present in the current appdomain.</returns>
        public static Assembly SearchCurrentDomain(string assemblyName)
        {
            if (!string.IsNullOrEmpty(assemblyName))
            {
                Assembly[] loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies();
                for (int i = 0; i < loadedAssemblies.Length; i++)
                {
                    Assembly assembly = loadedAssemblies[i];

                    if ((assembly.FullName.StartsWith(assemblyName + " ", StringComparison.OrdinalIgnoreCase)) ||
                        (assembly.FullName.StartsWith(assemblyName + ",", StringComparison.OrdinalIgnoreCase)))
                    {
                        return assembly;
                    }
                }
            }

            return null;
        }

        /// <summary>
        /// This function is used to get Type for the given Activity Class for the
        /// assemblies  loaded in the current appdomain.
        /// 
        /// If assembly is loaded in the current appdoamin it returns the reference to that assembly.
        /// </summary>
        /// <param name="assemblyName">Assembly Name.</param>
        /// <returns>Reference to the assembly if present in the current appdomain.</returns>
        public static Type SearchTypeIncurrentDomain(string assemblyClass)
        {
            Type assemblyType = null;
            try
            {
                Assembly[] loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies();
                foreach (Assembly assembly in loadedAssemblies)
                {
                    assemblyType = assembly.GetType(assemblyClass);

                    if (assemblyType != null) break;
                }
            }
            catch (Exception exception)
            {
                TridentErrorHandler.HandleAndThrowBusinessLogicException(exception, TridentErrorConstants.ErrorNumber4000069, "Search - Activity Class Failed in Current App Domain");
            }

            return assemblyType;

        }

        /// <summary>
        /// This funtcion is used to get all the referenced assemblies into 
        ///     the assembly collection.
        /// </summary>
        /// <param name="referredAssemblies">Collection of the referenced assemblies.</param>
        /// <param name="parentAssembly">Parent assembly.</param>
        private static void Load(Collection<Assembly> referredAssemblies, Assembly parentAssembly)
        {
            referredAssemblies.Add(parentAssembly);
            string path = Path.GetDirectoryName(parentAssembly.CodeBase).Substring("file:\\".Length);
            foreach (AssemblyName referredAssembly in parentAssembly.GetReferencedAssemblies())
            {
                string dllPath = Path.Combine(path, referredAssembly.Name + ".dll");
                string exePath = Path.Combine(path, referredAssembly.Name + ".exe");
                Assembly assembly = AssemblyLoad.SearchCurrentDomain(referredAssembly.Name);
                if (assembly == null)
                {
                    if (File.Exists(dllPath))
                    {
                        assembly = Assembly.LoadFrom(dllPath);
                    }
                    else if (File.Exists(exePath))
                    {
                        assembly = Assembly.LoadFrom(exePath);
                    }
                    else
                    {
                        assembly = Assembly.Load(referredAssembly);
                    }
                }

                if (!assembly.GlobalAssemblyCache && !referredAssemblies.Contains(assembly))
                {
                    Load(referredAssemblies, assembly);
                }
            }
        }
    }
}
